上一篇提供 .Net 2.0 的泛型委派來簡化委派宣告端所需撰寫的程式碼,進入 .Net 3.5 後,微軟提供了 Func 委派和 Action 委派,再進一步簡化我們委派宣告端的工作。
自學筆記這系列是我自己學習的一些心得分享,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。
另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。
PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行(大多是用 Statements 和 Program 模式)。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/。
跟著上一篇「LINQ自學筆記-打地基-泛型委派」最後的範例,我們發現,幾乎所有的委派,都可以透過預先定義之不同參數數量的泛型委派,省去開發人員自行撰寫委派結構定義的程式,只是我們還是得自行撰寫一次這些泛型委派,而且也要予以命名。這件事在實務上會造成兩個情況:
其實上述兩點的影響不算嚴重,但若能改進就更好啦,微軟很貼心的在 .Net 3.5 提出了 Func 委派和 Action 委派,它們有什麼特別?報告:一點都不特別,就只是把我們要自行撰寫的泛型委派先定義好,而且是放在 .Net Framework 中,方便使用,而且因為是官方提供的,所以大家使用時名稱都會固定就是 Func 和 Action,上述兩個問題就解決啦!
Func 委派有回傳值,Action 委派無回傳值,它們都位在 System 命名空間下。在 .Net 3.5 版本,兩者皆提供了無參數到 4 個參數的版本:
public delegate TResult Func<TResult>();
public delegate TResult Func<T, TResult>(T arg);
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2, T3 arg3);
public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3);
public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
public delegate void Action();
public delegate void Action<T>(T obj);
public delegate void Action<T1, T2>(T1 arg1, T2 arg2);
public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3);
public delegate void Action<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
有沒有發現上面這些泛型委派的定義,有一個不大一樣?是的,就是一個參數的 Action 委派,它的泛型方法參數名稱是「obj」,而不是「arg」,可知道為什麼嗎?
原因很簡單,因為 Action<in T> 這個泛型參數,是 .Net 2.0 就有提供,當時定義的泛型方法參數就是 obj,3.5 版本就繼續延用了。
再一個問題,有沒有覺得四個參數似乎不大夠,其實我也這麼覺得,但是 .Net 3.5 就只提供這樣了,幸好 .Net 4.0 就提供多達 0~ 16 個參數的 Func 和 Action 委派,實用!
Func委派、Action委派講完了耶!這篇文章這樣好像有點短,所以追加一個同事曾問過我的問題。我們有一些專案是 .Net 2.0 版本,後來升版到 3.5,他寫了類似下面的程式,試圖把自訂的委派轉型為 Func<int, int>,但失敗了:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CastDelegate
{
public delegate int MyDelegate(int num);//這是 .Net 2.0 時定義的委派
public class HouseMaster
{
//Cal 這是 .Net 3.5 同事新寫的方法,使用 Func 委派做為方法參數
public void Cal(Func<int, int> person, int Seed)
{
Console.WriteLine(person.Invoke(Seed));
}
}
public class Caller
{
public static int DoSomething(int num)
{
return num * num;
}
static void Main()
{
HouseMaster x = new HouseMaster();
MyDelegate dgA = DoSomething;
Func<int, int> dgB = (Func<int, int>)dgA;
x.Cal(dgB, 9);
}
}
}
//編譯結果:錯誤 1 無法將型別 'CastDelegate.MyDelegate' 轉換為 'System.Func<int,int>'
原因為何?
因為型別不一致啊,雖然方法參數都相同,但是請記得,他們是截然不同的型別,無法轉換也是理所當然。原因很清楚,但是找解法還是研究好一陣子,最終其實很簡單,只要下面這樣就行了:
static void Main()
{
HouseMaster x = new HouseMaster();
MyDelegate dgA = DoSomething;
//Func<int, int> dgB = (Func<int, int>)dgA;
Func<int, int> dgC = dgA.Invoke; //這行就是解法
x.Cal(dgC, 9);
}
下一篇,委派呼叫端將利用 Lambda 運算式,做進一步的簡化囉!下一篇也將是委派系列的最後一篇(呼…終於快搞定委派了……)